package org.option.testsuite.instruments.math_utils;

import com.alibaba.fastjson.JSONObject;
import org.option.testsuite.instruments.exception.ParamException;
import org.ujmp.core.DenseMatrix;
import org.ujmp.core.Matrix;

import java.util.HashMap;
import java.util.Map;

public class Fd_Compute_Ujump {

    private ParamFactory paramFactory;
    private double deltaS;
    private double deltaT;
    private double maxS;
    private double T;
    private int M;
    private int N;
    private double strike;
    private double[][] matrix;
    private String id;
    private Map<String,Matrix> paramMatrix = new HashMap<String,Matrix>();

    public Fd_Compute_Ujump(double deltaS, double deltaT, double maxS, double T,double strike){
        paramFactory = ParamFactory.getInstance();
        this.deltaT = deltaT;
        this.deltaS = deltaS;
        this.maxS = maxS;
        this.T = T;
        this.strike = strike;
        M = Double.valueOf((double) Math.ceil(maxS/deltaS)).intValue();
        N = Double.valueOf((double) Math.ceil(T/deltaT)).intValue();
        matrix = new double[M-1][M+1];
    }
    private Matrix makeBond() throws ParamException {
        Matrix dense = DenseMatrix.Factory.zeros(M+1, N+1);
        for(int i = 0; i < M+1; i++){
            dense.setAsFloat((float)Math.max(0,deltaS*i-strike),i,N);
        }
        for(int j = 0; j < N+1; j++) {
            dense.setAsDouble(maxS - strike, M, j);
        }
        //dense.setAsFloat(dense.getAsFloat(M-2,0) - computeParamC(paramFactory.getParam(deltaT*(N-1)),N-1),M-2,0);
        //System.out.println(dense);
        return dense;
    }
    public void compute() throws ParamException {
        Matrix dense = makeBond();
        //System.out.println(dense);
        for(int j = N; j > 0; j--){
            System.out.println("Computing Layer "+j);
            Matrix colJ = DenseMatrix.Factory.zeros(M-1, 1);
            for(int k = 0; k < M-1;k++){
                colJ.setAsFloat(dense.getAsFloat(k+1,j),k,0);
            }
            double minus = (maxS - strike)*computeParamC(paramFactory.getParam(N-1),M-1);
            colJ.setAsDouble(colJ.getAsFloat(M-2,0) - minus,M-2,0);
            Matrix convertMatrix = makeMatrixAndGetInverse(paramFactory.getParam(j));
           /* if(j == N-1){
                System.out.println(convertMatrix);
            }*/
            //System.out.println("old col "+colJ);
            colJ = convertMatrix.mtimes(colJ);
            //System.out.println("old new "+colJ);
            for(int k = 0; k < M-1;k++){
                dense.setAsFloat(colJ.getAsFloat(k,0),k+1,j-1);
            }
        }
        System.out.println("Last layer........\n"+dense);
        System.out.println(dense.getAsDouble(Math.round(1.0/deltaS),0));
        //System.out.println(lastLayer.getValueAt(float.valueOf(1.0/deltaS).intValue(),0));
    }
    private Matrix makeMatrixAndGetInverse(Fd_Params params){
        System.out.println("makeMatrixAndGetInverse......begin"+ JSONObject.toJSONString(params));
        String newid = String.valueOf(params.getVol().getVol())+String.valueOf(params.getRf().getRf())+String.valueOf(params.getQ().getQ());
        if(null != id &&id.equals(newid)){
            return paramMatrix.get(newid);
        }
        for(int i = 0; i < M-1; i++){
            double a = computeParamA(params,i+1);
            double b = computeParamB(params,i+1);
            double c = computeParamC(params,i+1);
            matrix[i][i] = a;
            matrix[i][i+1] = b;
            matrix[i][i+2] = c;
        }

        Matrix initMatrix = getCutMatrix(matrix);
        //System.out.println(initMatrix);
        Matrix convertMatrix = initMatrix.inv();
        id = String.valueOf(params.getVol().getVol())+String.valueOf(params.getRf().getRf())+String.valueOf(params.getQ().getQ());
        paramMatrix.put(id,convertMatrix);
        //System.out.println(convertMatrix.toString());
        System.out.println("makeMatrixAndGetInverse......end");
        return convertMatrix;
    }
    private Matrix  getCutMatrix(double initMatrix[][]){
        Matrix dense = DenseMatrix.Factory.zeros(initMatrix.length, initMatrix.length);
        for (int i = 0; i < initMatrix.length; i++) {
            for (int j = 1; j < initMatrix.length + 1; j++) {
                dense.setAsDouble(initMatrix[i][j],i,j-1);
            }
        }
        return dense;
    }
    private Double computeParamA(Fd_Params params,int j){
        return (Double) (0.5*((params.getRf().getRf()-params.getQ().getQ())*j -
                        params.getVol().getVol()*params.getVol().getVol()*j*j)*deltaT);
    }
    private double computeParamB(Fd_Params params,int j){
        return 1+ params.getVol().getVol()*params.getVol().getVol()*j*j*deltaT
                +(params.getRf().getRf())*deltaT;
    }
    private Double computeParamC(Fd_Params params,int j){
        return (Double) (-0.5*((params.getRf().getRf()-params.getQ().getQ())*j+params.getVol().getVol()*params.getVol().getVol()*j*j)*deltaT);
    }
}
